<?php

/*
	Question2Answer (c) Gideon Greenspan

	http://www.question2answer.org/

	
	File: qa-include/qa-app-limits.php
	Version: See define()s at top of qa-include/qa-base.php
	Description: Monitoring and rate-limiting user actions (application level)


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
    header('Location: ../');
    exit;
}


define('QA_LIMIT_QUESTIONS', 'Q');
define('QA_LIMIT_ANSWERS', 'A');
define('QA_LIMIT_COMMENTS', 'C');
define('QA_LIMIT_VOTES', 'V');
define('QA_LIMIT_REGISTRATIONS', 'R');
define('QA_LIMIT_LOGINS', 'L');
define('QA_LIMIT_UPLOADS', 'U');
define('QA_LIMIT_FLAGS', 'F');
define('QA_LIMIT_MESSAGES', 'M'); // i.e. private messages
define('QA_LIMIT_WALL_POSTS', 'W');


function qa_user_limits_remaining($action)
    /*
        Return how many more times the logged in user (and requesting IP address) can perform $action this hour,
        where $action is one of the QA_LIMIT_* constants defined above.
    */
{
    $userlimits = qa_db_get_pending_result('userlimits', qa_db_user_limits_selectspec(qa_get_logged_in_userid()));
    $iplimits = qa_db_get_pending_result('iplimits', qa_db_ip_limits_selectspec(qa_remote_ip_address()));

    return qa_limits_calc_remaining($action, @$userlimits[$action], @$iplimits[$action]);
}


function qa_limits_remaining($userid, $action)
    /*
        Return how many more times user $userid and/or the requesting IP can perform $action this hour, where $action is one
        of the QA_LIMIT_* constants above. This function is no longer used and only included for backwards compatibility.
    */
{
    if (qa_to_override(__FUNCTION__)) {
        $args = func_get_args();
        return qa_call_override(__FUNCTION__, $args);
    }

    require_once QA_INCLUDE_DIR . 'qa-db-limits.php';

    $dblimits = qa_db_limits_get($userid, qa_remote_ip_address(), $action);

    return qa_limits_calc_remaining($action, @$dblimits['user'], @$dblimits['ip']);
}


function qa_limits_calc_remaining($action, $userlimits, $iplimits)
    /*
        Calculate how many more times $action can be performed this hour, based on $userlimits and $iplimits retrieved from the database
    */
{
    switch ($action) {
        case QA_LIMIT_QUESTIONS:
            $usermax = qa_opt('max_rate_user_qs');
            $ipmax = qa_opt('max_rate_ip_qs');
            break;

        case QA_LIMIT_ANSWERS:
            $usermax = qa_opt('max_rate_user_as');
            $ipmax = qa_opt('max_rate_ip_as');
            break;

        case QA_LIMIT_COMMENTS:
            $usermax = qa_opt('max_rate_user_cs');
            $ipmax = qa_opt('max_rate_ip_cs');
            break;

        case QA_LIMIT_VOTES:
            $usermax = qa_opt('max_rate_user_votes');
            $ipmax = qa_opt('max_rate_ip_votes');
            break;

        case QA_LIMIT_REGISTRATIONS:
            $usermax = 1; // not really relevant
            $ipmax = qa_opt('max_rate_ip_registers');
            break;

        case QA_LIMIT_LOGINS:
            $usermax = 1; // not really relevant
            $ipmax = qa_opt('max_rate_ip_logins');
            break;

        case QA_LIMIT_UPLOADS:
            $usermax = qa_opt('max_rate_user_uploads');
            $ipmax = qa_opt('max_rate_ip_uploads');
            break;

        case QA_LIMIT_FLAGS:
            $usermax = qa_opt('max_rate_user_flags');
            $ipmax = qa_opt('max_rate_ip_flags');
            break;

        case QA_LIMIT_MESSAGES:
        case QA_LIMIT_WALL_POSTS:
            $usermax = qa_opt('max_rate_user_messages');
            $ipmax = qa_opt('max_rate_ip_messages');
            break;

        default:
            qa_fatal_error('Unknown limit code in qa_limits_calc_remaining: ' . $action);
            break;
    }

    $period = (int)(qa_opt('db_time') / 3600);

    return max(0, min(
        $usermax - ((@$userlimits['period'] == $period) ? $userlimits['count'] : 0),
        $ipmax - ((@$iplimits['period'] == $period) ? $iplimits['count'] : 0)
    ));
}


function qa_is_ip_blocked()
    /*
        Return whether the requesting IP address has been blocked from write operations
    */
{
    if (qa_to_override(__FUNCTION__)) {
        $args = func_get_args();
        return qa_call_override(__FUNCTION__, $args);
    }

    $blockipclauses = qa_block_ips_explode(qa_opt('block_ips_write'));

    foreach ($blockipclauses as $blockipclause)
        if (qa_block_ip_match(qa_remote_ip_address(), $blockipclause))
            return true;

    return false;
}


function qa_block_ips_explode($blockipstring)
    /*
        Return an array of the clauses within $blockipstring, each of which can contain hyphens or asterisks
    */
{
    $blockipstring = preg_replace('/\s*\-\s*/', '-', $blockipstring); // special case for 'x.x.x.x - x.x.x.x'

    return preg_split('/[^0-9\.\-\*]/', $blockipstring, -1, PREG_SPLIT_NO_EMPTY);
}


function qa_block_ip_match($ip, $blockipclause)
    /*
        Returns whether the ip address $ip is matched by the clause $blockipclause, which can contain a hyphen or asterisk
    */
{
    if (long2ip(ip2long($ip)) == $ip) {
        if (preg_match('/^(.*)\-(.*)$/', $blockipclause, $matches)) {
            if ((long2ip(ip2long($matches[1])) == $matches[1]) && (long2ip(ip2long($matches[2])) == $matches[2])) {
                $iplong = sprintf('%u', ip2long($ip));
                $end1long = sprintf('%u', ip2long($matches[1]));
                $end2long = sprintf('%u', ip2long($matches[2]));

                return (($iplong >= $end1long) && ($iplong <= $end2long)) || (($iplong >= $end2long) && ($iplong <= $end1long));
            }

        } elseif (strlen($blockipclause))
            return preg_match('/^' . str_replace('\\*', '[0-9]+', preg_quote($blockipclause, '/')) . '$/', $ip) > 0;
        // preg_quote misses hyphens but that is OK here
    }

    return false;
}


function qa_report_write_action($userid, $cookieid, $action, $questionid, $answerid, $commentid)
    /*
        Called after a database write $action performed by a user identified by $userid and/or $cookieid.
    */
{
}


function qa_limits_increment($userid, $action)
    /*
        Take note for rate limits that user $userid and/or the requesting IP just performed $action,
        where $action is one of the QA_LIMIT_* constants defined above.
    */
{
    if (qa_to_override(__FUNCTION__)) {
        $args = func_get_args();
        return qa_call_override(__FUNCTION__, $args);
    }

    require_once QA_INCLUDE_DIR . 'qa-db-limits.php';

    $period = (int)(qa_opt('db_time') / 3600);

    if (isset($userid))
        qa_db_limits_user_add($userid, $action, $period, 1);

    qa_db_limits_ip_add(qa_remote_ip_address(), $action, $period, 1);
}


/*
	Omit PHP closing tag to help avoid accidental output
*/